/******************************************************************************/
/*                                                                            */
/*  Copyright (c) 2009: ngi-central.org                                       */
/*  All rights reserved.                                                      */
/*                                                                            */
/******************************************************************************/


/// \file
/// This file implements the point geometric primitive.

#ifndef __sgl_point_h__
#define __sgl_point_h__

#include <sgl/global_settings.h>
#include <sgl/conditions.h>
#include <sgl/geometric_primitive.h>
#include <sgl/vector.h>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/static_assert.hpp>
#include <boost/operators.hpp>
#include <algorithm>

namespace sgl {


/******************************************************************************/
/*                                                                            */
/* point                                                                      */
/*                                                                            */
/******************************************************************************/

template <
	/// The dimensionality. 
	//!
	//! This defaults to two dimensions.
	size_t D=2,
	/// The element type. 
	//! 
	//! This type defaults to SGL_FPTYPE (which in turn defaults to double), but 
	//! any other numeric type is also suitable.
	typename T=SGL_FPTYPE>
/// A point in Euclidean space.
//!
//! \image html point.png
//!
//! A point can have any number of dimensions, from 1 to infinity. The 
//! dimensions of a point are specified with template parameter D. A point can 
//! have different coordinate types, such as int, float or double. The type is 
//! specified with template parameter T.
class point
	/// A point is a geometric primitive.
	: public geometric_primitive<D,T>
	, boost::equality_comparable<sgl::point<D,T>, sgl::point<D,T>
	, boost::additive<sgl::point<D,T>, sgl::vector<D,T>
	, boost::multiplicative<sgl::point<D,T>, T
	> > >
{
	// A point must have one or more dimensions.
	BOOST_STATIC_ASSERT(D >= 1);

public:
	/// Default constructor.
	//!
	//! Constructs a zero point. The contents of the values in the point are
	//! all initialized to zero.
	point()
	{ 
		for (size_t i=0; i<D; ++i)
			values_[i] = T();
		for (size_t i=0; i<D; ++i)
			SGL_POSTCONDITION(values_[i] == 0,
				"sgl::point::values_ is not correctly initialized.");
	}

	/// Default constructor for a two-dimensional point.
	point(
		/// The first element of the point.
		T x,
		/// The second element of the point.
		T y)
	{ 
		BOOST_STATIC_ASSERT(D == 2);

		values_[0] = x;
		values_[1] = y;
	}

	/// Default constructor for a three-dimensional point.
	point(
		/// The first element of the point.
		T x,
		/// The second element of the point.
		T y,
		/// The third element of the point.
		T z)
	{ 
		BOOST_STATIC_ASSERT(D == 3);

		values_[0] = x;
		values_[1] = y;
		values_[2] = z;
	}

	/// Copy constructor.
	point(
		/// The right hand side.
		sgl::point<D,T> const& rhs)
	{ 
		for (size_t i=0; i<D; ++i)
			values_[i] = rhs.values_[i];
		for (size_t i=0; i<D; ++i)
			SGL_POSTCONDITION(values_[i] == rhs.values_[i],
				"sgl::point::values_ is not correctly initialized.");
	}

	/// Copy assignment operator.
	sgl::point<D,T> & operator = (
		/// The right hand side.
		sgl::point<D,T> rhs)
	{
		swap(rhs);
		return *this;
	}

	/// Exchanges the elements of two point objects.
	void swap(
		/// The other point.
		sgl::point<D,T> & rhs)
	{
		SGL_PRECONDITION(D == rhs.dimensions(),
			"dimension mismatch.");

		for (size_t i=0; i<D; ++i)
			std::swap(values_[i], rhs.values_[i]);
	}

	/// Indexing operator.
	//!
	//! /return Returns a reference to the element at the specified index.
	T & operator [] (
		/// The index into the vector (zero based) used to access the point's 
		/// elements.
		size_t index)
	{
		SGL_PRECONDITION(index >= 0,
			"index parameter is out of range.");
		SGL_PRECONDITION(index < D,
			"index parameter is out of range.");

		return values_[index];
	}

	/// Indexing operator.
	//!
	//! /return Returns a const reference to the element at the specified index.
	T const & operator [] (
		/// The index into the point (zero based) used to access the point's 
		/// elements.
		size_t index) const
	{
		SGL_PRECONDITION(index >= 0,
			"index parameter is out of range.");
		SGL_PRECONDITION(index < D,
			"index parameter is out of range.");

		return values_[index];
	}

	/// Find the type of the geometric primitive.
	//!
	//! /return The type of the primitive.
	geometric_type type() const
	{
		return sgl::geometric_primitive<D,T>::point;
	} 

	/// Access the first element of the point.
	//!
	//! /return Returns a reference to the first element of the point.
	T & x()
	{
		BOOST_STATIC_ASSERT(D >= 1);
		return values_[0];
	}

	/// Access the first element of the point.
	//!
	//! /return Returns a const reference to the first element of the point.
	T const & x() const
	{
		BOOST_STATIC_ASSERT(D >= 1);
		return values_[0];
	}

	/// Access the second element of the point.
	//!
	//! /return Returns a reference to the second element of the point.
	T & y()
	{
		BOOST_STATIC_ASSERT(D >= 2);
		return values_[1];
	}

	/// Access the second element of the point.
	//!
	//! /return Returns a const reference to the second element of the point.
	T const & y() const
	{
		BOOST_STATIC_ASSERT(D >= 2);
		return values_[1];
	}

	/// Access the third element of the point.
	//!
	//! /return Returns a reference to the third element of the point.
	T & z()
	{
		BOOST_STATIC_ASSERT(D >= 3);
		return values_[2];
	}

	/// Access the third element of the point.
	//!
	//! /return Returns a const reference to the third element of the point.
	T const & z() const
	{
		BOOST_STATIC_ASSERT(D >= 3);
		return values_[2];
	}

	/// Unary negation.
	//!
	//! A point is negated by negating all its components.
	//!
	//! \return A negated point object.
    sgl::point<D,T> operator - () const
    {
		sgl::point<D,T> temp(*this);
		std::transform(values_, values_+D, temp.values_, 
			- boost::phoenix::arg_names::arg1);
        return temp;
    }

	/// Equality operator.
	//!
	//! Two points are compared for equality by comparing their components in a
	//! pairwise manner. The points are equal only if all pairwise components
	//! are equal.
	//!
	//! Through the use of boost::equality as a base class operator != is 
	//! defined as well and implemented in terms of this operator ==.
	//!
	//! \return true if equal, false otherwise.
    bool operator == (
		/// Right hand side.
		sgl::point<D,T> const& rhs) const
    {
		SGL_PRECONDITION(D == rhs.dimensions(),
			"dimension mismatch.");

		return std::equal(values_, values_+D, rhs.values_);
    }

	/// Add a point and a vector.
	//!
	//! Adding a vector to a point moves the point in direction of the vector.
	//! Addition is implemented by pairwise addition of the components of the 
	//! point and the vector.
	//!
	//! Through the use of boost::additive as a base class operator + is 
	//! defined as well and implemented in terms of this operator +=.
	//!
	//! \return A reference to this object.
    sgl::point<D,T> & operator += (
		/// Right hand side.
		sgl::vector<D,T> const& rhs)
    {
		SGL_PRECONDITION(D == rhs.dimensions(),
			"dimension mismatch.");

		for (size_t i=0; i<D; ++i)
			operator [] (i) += rhs[i];

        return *this;
    }

	/// Subtract a vector from a point.
	//!
	//! Subtracting a vector from a point moves the point in the opposite 
	//! direction of the vector. Subtraction is implemented by pairwise 
	//! subtraction of the components of the point and the vector.
	//!
	//! Through the use of boost::additive as a base class operator - is 
	//! defined as well and implemented in terms of this operator -=.
	//!
	//! \return A reference to this object.
    sgl::point<D,T> & operator -= (
		/// Right hand side.
		sgl::vector<D,T> const& rhs)
    {
		SGL_PRECONDITION(D == rhs.dimensions(),
			"dimension mismatch.");

		for (size_t i=0; i<D; ++i)
			operator [] (i) -= rhs[i];

		return *this;
    }

	/// Subtract two points.
	//!
	//! Subtracting two points yields a vector as a result. Subtracting two
	//! points is implemented by pairwise subtraction of the components of the
	//! two points.
	//!
	//! \return A vector.
	sgl::vector<D,T> operator - (
		/// Right hand side.
		sgl::point<D,T> const& rhs) const
	{
		sgl::vector<D,T> result;

		for (size_t i=0; i<D; ++i)
			result[i] = operator [] (i) - rhs[i];

		return result;
	}

	/// Scale a point by multiplication.
	//!
	//! A point is scaled by multiplying each of its components with the 
	//! factor.
	//!
	//! Through the use of boost::multiplicative as a base class operator * is 
	//! defined as well and implemented in terms of this operator *=.
	//!
	//! \return A reference to this object.
	sgl::point<D,T> & operator *= (
		/// The scaling factor.
		T const& factor)
	{
		for (T * i=values_; i!=values_+D; ++i)
			(*i) *= factor;

		return *this;
	}

	/// Scale a point by division.
	//!
	//! A point is scaled by dividing each of its components through the 
	//! divisor.
	//!
	//! Through the use of boost::multiplicative as a base class operator / is 
	//! defined as well and implemented in terms of this operator /=.
	//!
	//! \return A reference to this object.
	sgl::point<D,T> & operator /= (
		/// The scaling factor.
		T const& divisor)
	{
		for (T * i=values_; i!=values_+D; ++i)
			(*i) /= divisor;

		return *this;
	}

private:
	// The components of the point.
	T values_[D];
};


/// A two-dimensional point with the default component type.
typedef sgl::point<2> point2;

/// A two-dimensional point with integer component type.
typedef sgl::point<2, int> point2i;

/// A two-dimensional point with float component type.
typedef sgl::point<2, float> point2f;

/// A two-dimensional point with double component type.
typedef sgl::point<2, double> point2d;


/// A three-dimensional point with the default component type.
typedef sgl::point<3> point3;

/// A three-dimensional point with integer component type.
typedef sgl::point<3, int> point3i;

/// A three-dimensional point with float component type.
typedef sgl::point<3, float> point3f;

/// A three-dimensional point with double component type.
typedef sgl::point<3, double> point3d;


} // namespace sgl

#endif // __sgl_point_h__
